package com.lipaluma.parser;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lipaluma.config.Configuration;
import com.lipaluma.context.Context;
import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.exceptions.CsvParserProcessException;
import com.lipaluma.exceptions.CsvParserValidationException;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.merge.MergingTargetFunction;
import com.lipaluma.field.merge.TargetMergedAlgorithm;
import com.lipaluma.field.validator.strategies.ValidationStrategyEnum;
import com.lipaluma.io.Row;
import com.lipaluma.io.SourceReader;
import com.lipaluma.io.SourceWriter;
import com.lipaluma.parser.marshalling.FieldMarshalled;
import com.lipaluma.parser.marshalling.ObjectMarshalled;
import com.lipaluma.parser.marshalling.OneToManyMarshallerStrategy;
import com.lipaluma.parser.post.treatment.unmarshall.AllLinesTreatmentProcessor;
import com.lipaluma.parser.post.treatment.unmarshall.merge.MergeLinesProcessor;
import com.lipaluma.xml.XmlMappings;

/**
 * Abstract Mapper
 * 
 * @see {@link ColumnDataParser}, {@link AbstractDataParserIterable}
 * 
 * @author Mario
 *
 * @param <TARGET>
 */
public abstract class AbstractAllDataParser<TARGET, CONF extends Configuration> extends AbstractDataParser<TARGET, CONF> implements DataParser<TARGET>{
	private static Logger LOG = LoggerFactory.getLogger(AbstractAllDataParser.class);
	
	protected List<AllLinesTreatmentProcessor<TARGET>> allLinesTreatmentProcessors = new ArrayList<AllLinesTreatmentProcessor<TARGET>>();
	private OneToManyMarshallerStrategy oneToManyMarshallerStrategy;
	
	protected AbstractAllDataParser() {}
	protected AbstractAllDataParser(AbstractAllDataParser.Builder<? extends AbstractAllDataParser<TARGET, CONF>, TARGET, CONF> builder) {
		super(builder);
		MergeLinesProcessor<TARGET> processor;
		if(builder.mergingTargetFunction != null) {
			processor = new MergeLinesProcessor<TARGET>(builder.targetClass, builder.mergingTargetFunction);
		} else {
			processor = new MergeLinesProcessor<TARGET>(builder.targetClass, builder.targetMergeable);
		}
		if(processor.init(fieldMappers)) {
			allLinesTreatmentProcessors.add(processor);			
		}
		this.allLinesTreatmentProcessors.addAll(builder.processors);
	}

	protected AbstractAllDataParser(Class<TARGET> targetClass) {
		super(targetClass);
		MergeLinesProcessor<TARGET> processor = new MergeLinesProcessor<TARGET>();
		if(processor.init(fieldMappers))
			allLinesTreatmentProcessors.add(processor);
	}
	
	protected AbstractAllDataParser(Class<TARGET> targetClass, XmlMappings xmlMappings) {
		super(targetClass, xmlMappings);
		MergeLinesProcessor<TARGET> processor = new MergeLinesProcessor<TARGET>();
		if(processor.init(fieldMappers, xmlMappings))
			allLinesTreatmentProcessors.add(processor);
	}
	
	/**
	 * process All rows and returns the list of objects Mapped
	 * 
	 */
	protected List<TARGET> unmarshalAll(SourceReader fileReader) {
		try {
			List<TARGET> result = new ArrayList<TARGET>();
			StringBuilder errorMessage = new StringBuilder();
			while (fileReader.hasNext()) {
				TARGET target;
				try {
					target = unmarshal(fileReader.next());
					result.add(target);
				} catch (CsvParserValidationException e) {
					switch(validationStrategy) {
					case STOP_ON_FIRST_VALIDATION_ERROR:
						LOG.error("Validation Error [line "+fileReader.getCurrentLineNumber()+"] ", e);
						throw new CsvParserProcessException("Validation Error [line "+fileReader.getCurrentLineNumber()+"]", e);
					case LET_FINISH_AND_SHOW_ERRORS : 
						LOG.info("Validation Error [line "+fileReader.getCurrentLineNumber()+"] "+e.getMessage());
						errorMessage.append("Validation Error [line ").append(fileReader.getCurrentLineNumber()).append("]");
						errorMessage.append(e.getMessage());
						break;
					case FILTER_VALIDATION_ERRORS :
						LOG.warn("Validation Error [line "+fileReader.getCurrentLineNumber()+"]\n"+e.getMessage());
						LOG.warn("The line will be filtered.");
						errorMessage.append(">>>>>>>>>>>>>>> Validation Error [line ").append(fileReader.getCurrentLineNumber()).append("] <<<<<<<<<<<<<<<\n");
						errorMessage.append(e.getMessage()).append("\n");
					}
				}
			}
			if(StringUtils.isNotEmpty(errorMessage.toString())) {
				LoggerFactory.getLogger("result.error.file").error(errorMessage.toString());
				if(validationStrategy == ValidationStrategyEnum.LET_FINISH_AND_SHOW_ERRORS)
					throw new CsvParserProcessException("Error on process.\n"+errorMessage);
			}
			for(AllLinesTreatmentProcessor<TARGET> processor : allLinesTreatmentProcessors)
				result = processor.execute(result);
			return result;
		} finally {
			if(fileReader != null)
				fileReader.close();
		}
	}

	protected void marshalAll(Collection<TARGET> objects, SourceWriter fw) {
		List<ObjectMarshalled> objectsMarshalled = convertToObjectsMarshalled(objects);
		for (ObjectMarshalled objectMarshalled : objectsMarshalled) {
			Row row = fw.createNewRow();
			for(FieldMarshalled fieldMarshalled : objectMarshalled.getFieldsMarshalled())
				row.writeValue(fieldMarshalled.getValue(), fieldMarshalled.getMapping());
			createOneToManyRows(fw, objectMarshalled, row);
		}
		fw.close();
	}	

	private void createOneToManyRows(SourceWriter fw, ObjectMarshalled objectMarshalled, Row row) {
		ArrayList<Row> rows = new ArrayList<Row>();
		rows.add(row);
		createOneToManyRows(fw, objectMarshalled, rows);
	}
	private void createOneToManyRows(SourceWriter fw, ObjectMarshalled objectMarshalled, List<Row> rows) {
		for (List<ObjectMarshalled> oneToManyFieldExploded : objectMarshalled.getOneToManyFieldsMarshalled()) {
			List<List<Row>> listOfRowsToWork = new ArrayList<List<Row>>();
			listOfRowsToWork.add(rows);
			for(int i=0; i<oneToManyFieldExploded.size() - 1; i++)
				listOfRowsToWork.add(duplicateRows(fw, rows));
			for (int i=0; i<oneToManyFieldExploded.size(); i++) {
				List<Row> rowsToWork = listOfRowsToWork.get(i);
				ObjectMarshalled singleObject = oneToManyFieldExploded.get(i);
				for (Row row : rowsToWork) {
					for(FieldMarshalled fieldMarshalled : singleObject.getFieldsMarshalled())
						row.writeValue(fieldMarshalled.getValue(), fieldMarshalled.getMapping());
				}
				createOneToManyRows(fw, singleObject, rowsToWork);
				if(i > 0)
					rows.addAll(rowsToWork);
			}
		}

	}
	
	private List<Row> duplicateRows(SourceWriter fw, List<Row> rows) {
		List<Row> result = new ArrayList<Row>();
		for (Row row : rows) {
			result.add(fw.duplicateRow(row));
		}
		return result;
	}
	
	protected List<ObjectMarshalled> convertToObjectsMarshalled(Collection<TARGET> objects) {
		if(objects == null)
			throw new CsvParserException("The collection of objects cannot be null");

		int objectNumber = 0;
		StringBuilder errorMessage = new StringBuilder();
		List<ObjectMarshalled> objectsMarshalled = new ArrayList<ObjectMarshalled>();
		for (TARGET object : objects) {
			try {
				objectNumber++;
				objectsMarshalled.add(marshal(object));
			} catch (CsvParserValidationException e) {
				switch(validationStrategy) {
				case STOP_ON_FIRST_VALIDATION_ERROR:
					LOG.error("Validation Error [object number "+objectNumber+"]", e);
					throw new CsvParserProcessException("Validation Error [line "+objectNumber+"]", e);
				case LET_FINISH_AND_SHOW_ERRORS : 
					LOG.info("Validation Error [object number "+objectNumber+"]"+e.getMessage());
					errorMessage.append("Validation Error [line ").append(objectNumber).append("]");
					errorMessage.append(e.getMessage());
					break;
				case FILTER_VALIDATION_ERRORS :
					LOG.warn("Validation Error [object number "+objectNumber+"]"+e.getMessage());
					LOG.warn("The line will be filtered.");
					errorMessage.append("Validation Error [line ").append(objectNumber).append("]");
					errorMessage.append(e.getMessage());
				}
			}
		}
		if(StringUtils.isNotEmpty(errorMessage.toString())) {
			LoggerFactory.getLogger("result.error.file").error(errorMessage.toString());
			if(validationStrategy == ValidationStrategyEnum.LET_FINISH_AND_SHOW_ERRORS)
				throw new CsvParserProcessException("Error on process.\n"+errorMessage);
		}
		return objectsMarshalled;
	}

	/**
	 * change the validation strategy
	 * @param strategy the validation strategy
	 */
	public void setValidationStrategy(ValidationStrategyEnum strategy) {
		this.validationStrategy = strategy;
	}

	public abstract static class Builder<PARSER extends AbstractAllDataParser<TARGET, CONF>, TARGET, CONF extends Configuration> extends AbstractDataParser.Builder<PARSER, TARGET, CONF> {
		private List<AllLinesTreatmentProcessor<TARGET>> processors = new ArrayList<AllLinesTreatmentProcessor<TARGET>>();
		private MergingTargetFunction mergingTargetFunction;
		private boolean targetMergeable;
		
		protected Builder(Class<TARGET> targetClass, Context context) {
			super(targetClass, context);
		}
		@Override
		public FromParserFieldMapperBuilder<? extends Builder<PARSER, TARGET, CONF>, TARGET> declareFields() {
			FromParserFieldMapperBuilder<? extends Builder<PARSER, TARGET, CONF>, TARGET> builder = FromParserFieldMapperBuilder.create(this, this.targetClass);
			this.context = builder.getContext();
			return builder;
		}
		
		@Override
		public Builder<PARSER, TARGET, CONF> withConfiguration(CONF configuration) {
			super.withConfiguration(configuration);
			return this;
		}
		
		@Override
		public Builder<PARSER, TARGET, CONF> withValidationStrategy(ValidationStrategyEnum validationStrategy) {
			super.withValidationStrategy(validationStrategy);
			return this;
		}
		@Override
		public Builder<PARSER, TARGET, CONF> declareFields(List<FieldMapper> fieldMappers) {
			super.declareFields(fieldMappers);
			return this;
		}
		@Override
		public Builder<PARSER, TARGET, CONF> withFieldMappers(List<FieldMapper> mappers) {
			super.withFieldMappers(mappers);
			return this;
		}

		public Builder<PARSER, TARGET, CONF> targetMergeable(boolean mergeable) {
			this.targetMergeable = true;
			return this;
		}

		public Builder<PARSER, TARGET, CONF> targetMergeableWith(TargetMergedAlgorithm algorithm, String... params) {
			this.targetMergeable = true;
			this.mergingTargetFunction = new MergingTargetFunction(algorithm, params);
			return this;
		}
		
		public Builder<PARSER, TARGET, CONF> addNewLinesTreatmentProcessor(AllLinesTreatmentProcessor<TARGET> processor) {
			processors.add(processor);
			return this;
		}
		
	}

}